import { isRef, onUnmounted, unref, watch, Reactive, Ref, ref } from 'vue';
import { VxeFormInstance, VxeFormProps, VxeFormMethods } from 'vxe-table';
import { isObject, get } from 'lodash-es';
import { VxeFormEventProps, VxeGlobalComponents } from 'vxe-pc-ui';
import { VxeFormItemProps, VxeFormItemPropTypes } from 'vxe-pc-ui/types/components/form-item';
import { warn, error } from '@/utils/log';
import { VxeFormDefines } from 'vxe-pc-ui/types/components/form';
// dynamic use hook props
type Recordable<D = any> = Record<string, D>;
type PropValue<T> = T extends keyof FormPropsVxe ? FormPropsVxe[T] : never;

export interface FormMethods<D = any> extends VxeFormMethods {
  setProps(props: FormPropsVxe<D>): void;
  setPropField<T extends keyof FormPropsVxe>(prop: T, value: PropValue<T>): void;
  setLoading(flag?: boolean): boolean;
  getLoading(): boolean;
  getPropField<T extends keyof FormPropsVxe>(prop: T): PropValue<T>;
  getProps(): FormPropsVxe<D>;
  getFormData<T extends keyof D>(prop?: T): D;
  getFormRef(): Promise<VxeFormInstance<D>>;
  getRulesAll(): VxeFormItemProps['Rules'];
  onSubmit(): Promise<{
    formData: D;
    $form: VxeFormInstance<D>;
    propsRef: FormPropsVxe<D>;
  }>;
}
// @ts-ignore eslint-no-explicit-any
// export type PropsByName<N extends keyof VxeGlobalComponents> = N extends keyof VxeGlobalComponents
//   ? VxeGlobalComponents[N]['props']
//   : Record<string, any>;
// TODO props types tips
export type PropsByName<N extends keyof VxeGlobalComponents> = N extends keyof VxeGlobalComponents
  ? any
  : Record<string, any>;

export interface ItemRender<
  P = Record<string, any>,
  N extends keyof VxeGlobalComponents = keyof VxeGlobalComponents,
> extends Omit<VxeFormItemPropTypes.ItemRender<P>, 'props' | 'name'> {
  name?:
    | N
    | 'ApiSelect'
    | 'ApiCheckbox'
    | 'ApiCascader'
    | 'ApiRadio'
    | 'ApiTreeSelect'
    | 'ElTreeSelect'
    | 'ElInput'
    | 'ElInputNumber'
    // | 'ElRate'
    | 'ElCheckbox'
    | 'ElRadio'
    | 'ElSwitch'
    | 'ElSelect'
    | 'ElCascader'
    | 'ElDatePicker'
    // | 'ElUpload'
    | 'ImageUpload'
    | 'FileUpload';
  props?: PropsByName<N> | P;
}
export interface FormItemProps<D = any> extends Omit<VxeFormItemProps<D>, 'Rules' | 'itemRender'> {
  itemRender?: ItemRender;
  rules?: VxeFormItemPropTypes.Rules<D>;
}
export interface FormProps<D = any> extends Omit<VxeFormProps<D>, 'items'> {
  items?: FormItemProps[];
}

export interface FormPropsVxe<D = any> extends FormProps<D>, VxeFormEventProps {
  formData?: D | Ref<D> | Reactive<D>;
  // [key: string]: any;
}

export function getDynamicProps<D extends Record<string, unknown>, U>(props: D): Partial<U> {
  const ret: Recordable = {};
  Object.keys(props).map((key) => {
    ret[key] = unref((props as Recordable)[key]);
  });
  return ret as Partial<U>;
}

export function useVxeFormInstance<T = any>(
  props?: Ref<FormPropsVxe<T>> | Reactive<FormPropsVxe<T>> | FormPropsVxe<T>,
): [
  (methods: FormMethods<T> & VxeFormInstance<T>, callback: (props: FormPropsVxe) => void) => any,
  FormMethods<T>,
] {
  const formRef = ref<FormMethods & VxeFormInstance>();
  const propsRef = ref<FormPropsVxe>();

  async function getForm() {
    const form = unref(formRef);
    // log(form);
    if (!form) {
      error(
        'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
      );
    }
    await nextTick();
    return form as FormMethods & VxeFormInstance;
  }

  function register(
    _methods: FormMethods<T> & VxeFormInstance,
    callback: (props: FormPropsVxe) => void,
  ) {
    if (!getCurrentInstance()) {
      error('useVxeFormInstance() can only be used inside setup() or functional components!');
    }
    onUnmounted(() => {
      formRef.value = undefined;
      propsRef.value = undefined;
    });
    if (_methods === unref(formRef) && propsRef.value !== undefined) return;
    console.log(_methods, '_methods');

    // warn(_methods);
    formRef.value = _methods;

    watch(
      () => props,
      () => {
        // 同步更新propsRef数据
        if (isObject(props)) {
          propsRef.value = isRef(props) ? props.value : props;
          if (propsRef.value.formData === undefined) {
            warn('useVxeFormInstance props.formData is not find (ref or reactive!!)');
            propsRef.value.formData = {};
            // throw Error('useVxeFormInstance props -> formData is not find (ref or reactive!!)');
          }
          // @ts-ignore
          unref(formRef)?.setProps({
            ...getDynamicProps(propsRef.value as Recordable),
          });
        }
      },
      {
        immediate: true,
        deep: true,
      },
    );

    callback(<FormPropsVxe>unref(propsRef));
  }

  const methods: Partial<FormMethods<T>> = {
    getFormData(prop): any {
      const formData = unref(formRef)?.getPropField('formData');
      if (isObject(formData) && typeof prop === 'string') {
        return get(formData, prop);
      }
      return formData;
    },
    async getFormRef() {
      return await getForm();
    },

    getPropField<T extends keyof FormPropsVxe>(prop: T): PropValue<T> {
      return unref(formRef)?.getPropField(prop) as PropValue<T>;
    },
    setPropField<T extends keyof FormPropsVxe>(prop: T, value: PropValue<T>): void {
      return unref(formRef)?.setPropField(prop, value);
    },

    setProps(props: FormPropsVxe) {
      return unref(formRef)?.setProps(props);
    },

    setLoading(flag) {
      const loadingProp = 'loading';
      const state: boolean = flag !== undefined ? flag : !unref(formRef)?.getPropField(loadingProp);
      unref(formRef)?.setPropField(loadingProp, state);
      return state;
    },

    getLoading() {
      return unref(formRef)?.getPropField('loading') as boolean;
    },

    getProps(): FormPropsVxe {
      return formRef.value?.getProps?.() as FormPropsVxe;
    },

    async reset() {
      (await getForm()).setPropField('formData', {});
      return formRef.value?.reset?.() as Promise<any>;
    },
    dispatchEvent(type, params, evnt) {
      return unref(formRef)?.dispatchEvent(type, params, evnt);
    },

    validate(callback) {
      return unref(formRef)?.validate(callback) as Promise<VxeFormDefines.ValidateErrorMapParams>;
    },
    validateField(field) {
      return unref(formRef)?.validateField(field) as Promise<VxeFormDefines.ValidateErrorMapParams>;
    },
    clearValidate(field) {
      return unref(formRef)?.clearValidate(field) as Promise<any>;
    },
    updateStatus(params) {
      return unref(formRef)?.updateStatus(params);
    },
    toggleCollapse() {
      return unref(formRef)?.toggleCollapse() as Promise<any>;
    },
    getItems() {
      return unref(formRef)?.getItems() as VxeFormDefines.ItemInfo[];
    },
    getItemByField(field) {
      return unref(formRef)?.getItemByField(field) as VxeFormDefines.ItemInfo | null;
    },
    closeTooltip() {
      return unref(formRef)?.closeTooltip() as Promise<any>;
    },

    onSubmit() {
      return unref(formRef)?.onSubmit() as Promise<{
        formData: T;
        $form: VxeFormInstance<T>;
        propsRef: FormPropsVxe<T>;
      }>;
    },
    getRulesAll() {
      return unref(formRef)?.getRulesAll();
    },
  };

  return [register, methods as FormMethods<T>];
}
